package com.example.layoutmanagerdemo.detach;

import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.example.layoutmanagerdemo.detach.RecyclerView.ALLOW_THREAD_GAP_WORK;
import static com.example.layoutmanagerdemo.detach.RecyclerView.DEBUG;
import static com.example.layoutmanagerdemo.detach.RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST;
import static com.example.layoutmanagerdemo.detach.RecyclerView.FOREVER_NS;
import static com.example.layoutmanagerdemo.detach.RecyclerView.findNestedRecyclerView;
import static com.example.layoutmanagerdemo.detach.RecyclerView.getChildViewHolderInt;

public final class Recycler {
    private static final String TAG = Recycler.class.getSimpleName();
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;
    RecycledViewPool mRecyclerPool;
    private ViewCacheExtension mViewCacheExtension;
    static final int DEFAULT_CACHE_SIZE = 2;

    private RecyclerView recyclerView;

    public Recycler(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
    }

    public void clear() {
        mAttachedScrap.clear();
        recycleAndClearCachedViews();
    }

    public void setViewCacheSize(int viewCount) {
        mRequestedCacheMax = viewCount;
        updateViewCacheSize();
    }

    void updateViewCacheSize() {
        int extraCache = recyclerView.mLayout != null ? recyclerView.mLayout.mPrefetchMaxCountObserved : 0;
        mViewCacheMax = mRequestedCacheMax + extraCache;
        for (int i = mCachedViews.size() - 1;
             i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
            recycleCachedViewAt(i);
        }
    }

    @NonNull
    public List<ViewHolder> getScrapList() {
        return mUnmodifiableAttachedScrap;
    }

    boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
        if (holder.isRemoved()) {
            if (DEBUG && !recyclerView.mState.isPreLayout()) {
                throw new IllegalStateException("should not receive a removed view unless it"
                        + " is pre layout" + recyclerView.exceptionLabel());
            }
            return recyclerView.mState.isPreLayout();
        }
        if (holder.mPosition < 0 || holder.mPosition >= recyclerView.mAdapter.getItemCount()) {
            throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
                    + "adapter position" + holder + recyclerView.exceptionLabel());
        }
        if (!recyclerView.mState.isPreLayout()) {
            final int type = recyclerView.mAdapter.getItemViewType(holder.mPosition);
            if (type != holder.getItemViewType()) {
                return false;
            }
        }
        if (recyclerView.mAdapter.hasStableIds()) {
            return holder.getItemId() == recyclerView.mAdapter.getItemId(holder.mPosition);
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
                                                int position, long deadlineNs) {
        holder.mOwnerRecyclerView = recyclerView;
        final int viewType = holder.getItemViewType();
        long startBindNs = recyclerView.getNanoTime();
        if (deadlineNs != FOREVER_NS
                && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
            // abort - we have a deadline we can't meet
            return false;
        }
        recyclerView.mAdapter.bindViewHolder(holder, offsetPosition);
        long endBindNs = recyclerView.getNanoTime();
        mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
        attachAccessibilityDelegateOnBind(holder);
        if (recyclerView.mState.isPreLayout()) {
            holder.mPreLayoutPosition = position;
        }
        return true;
    }

    public void bindViewToPosition(@NonNull View view, int position) {
        ViewHolder holder = getChildViewHolderInt(view);
        if (holder == null) {
            throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
                    + " pass arbitrary views to this method, they should be created by the "
                    + "Adapter" + recyclerView.exceptionLabel());
        }
        final int offsetPosition = recyclerView.mAdapterHelper.findPositionOffset(position);
        if (offsetPosition < 0 || offsetPosition >= recyclerView.mAdapter.getItemCount()) {
            throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                    + "position " + position + "(offset:" + offsetPosition + ")."
                    + "state:" + recyclerView.mState.getItemCount() + recyclerView.exceptionLabel());
        }
        tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);

        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final ReLayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (ReLayoutParams) recyclerView.generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!recyclerView.checkLayoutParams(lp)) {
            rvLayoutParams = (ReLayoutParams) recyclerView.generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (ReLayoutParams) lp;
        }

        rvLayoutParams.mInsetsDirty = true;
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null;
    }

    public int convertPreLayoutPositionToPostLayout(int position) {
        if (position < 0 || position >= recyclerView.mState.getItemCount()) {
            throw new IndexOutOfBoundsException("invalid position " + position + ". State "
                    + "item count is " + recyclerView.mState.getItemCount() + recyclerView.exceptionLabel());
        }
        if (!recyclerView.mState.isPreLayout()) {
            return position;
        }
        return recyclerView.mAdapterHelper.findPositionOffset(position);
    }

    @NonNull
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }

    View getViewForPosition(int position, boolean dryRun) {
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }

    @Nullable
    ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                                                     boolean dryRun, long deadlineNs) {
        if (position < 0 || position >= recyclerView.mState.getItemCount()) {
            throw new IndexOutOfBoundsException("Invalid item position " + position
                    + "(" + position + "). Item count:" + recyclerView.mState.getItemCount()
                    + recyclerView.exceptionLabel());
        }
        boolean fromScrapOrHiddenOrCache = false;
        ViewHolder holder = null;
        if (recyclerView.mState.isPreLayout()) {
            holder = getChangedScrapViewForPosition(position);
            fromScrapOrHiddenOrCache = holder != null;
        }
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            if (holder != null) {
                if (!validateViewHolderForOffsetPosition(holder)) {
                    if (!dryRun) {
                        holder.addFlags(ViewHolder.FLAG_INVALID);
                        if (holder.isScrap()) {
                            recyclerView.removeDetachedView(holder.itemView, false);
                            holder.unScrap();
                        } else if (holder.wasReturnedFromScrap()) {
                            holder.clearReturnedFromScrapFlag();
                        }
                        recycleViewHolderInternal(holder);
                    }
                    holder = null;
                } else {
                    fromScrapOrHiddenOrCache = true;
                }
            }
        }
        if (holder == null) {
            final int offsetPosition = recyclerView.mAdapterHelper.findPositionOffset(position);
            if (offsetPosition < 0 || offsetPosition >= recyclerView.mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                        + "position " + position + "(offset:" + offsetPosition + ")."
                        + "state:" + recyclerView.mState.getItemCount() + recyclerView.exceptionLabel());
            }

            final int type = recyclerView.mAdapter.getItemViewType(offsetPosition);
            if (recyclerView.mAdapter.hasStableIds()) {
                holder = getScrapOrCachedViewForId(recyclerView.mAdapter.getItemId(offsetPosition),
                        type, dryRun);
                if (holder != null) {
                    holder.mPosition = offsetPosition;
                    fromScrapOrHiddenOrCache = true;
                }
            }
            if (holder == null && mViewCacheExtension != null) {
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = recyclerView.getChildViewHolder(view);
                    if (holder == null) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view which does not have a ViewHolder"
                                + recyclerView.exceptionLabel());
                    } else if (holder.shouldIgnore()) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view that is ignored. You must call stopIgnoring before"
                                + " returning this view." + recyclerView.exceptionLabel());
                    }
                }
            }
            if (holder == null) { // fallback to pool
                if (DEBUG) {
                    Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                            + position + ") fetching from shared pool");
                }
                holder = getRecycledViewPool().getRecycledView(type);
                if (holder != null) {
                    holder.resetInternal();
                    if (FORCE_INVALIDATE_DISPLAY_LIST) {
                        invalidateDisplayListInt(holder);
                    }
                }
            }
            if (holder == null) {
                long start = recyclerView.getNanoTime();
                if (deadlineNs != FOREVER_NS
                        && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                    return null;
                }
                holder = recyclerView.mAdapter.createViewHolder(recyclerView, type);
                if (ALLOW_THREAD_GAP_WORK) {
                    RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                    if (innerView != null) {
                        holder.mNestedRecyclerView = new WeakReference<>(innerView);
                    }
                }

                long end = recyclerView.getNanoTime();
                mRecyclerPool.factorInCreateTime(type, end - start);
                if (DEBUG) {
                    Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
                }
            }
        }
        if (fromScrapOrHiddenOrCache && !recyclerView.mState.isPreLayout() && holder
                .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
            holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
            if (recyclerView.mState.mRunSimpleAnimations) {
                int changeFlags = ItemAnimator
                        .buildAdapterChangeFlagsForAnimations(holder);
                changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                final ItemAnimator.ItemHolderInfo info = recyclerView.mItemAnimator.recordPreLayoutInformation(recyclerView.mState,
                        holder, changeFlags, holder.getUnmodifiedPayloads());
                recyclerView.recordAnimationInfoIfBouncedHiddenView(holder, info);
            }
        }
        boolean bound = false;
        if (recyclerView.mState.isPreLayout() && holder.isBound()) {
            holder.mPreLayoutPosition = position;
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            if (DEBUG && holder.isRemoved()) {
                throw new IllegalStateException("Removed holder should be bound and it should"
                        + " come here only in pre-layout. Holder: " + holder
                        + recyclerView.exceptionLabel());
            }
            final int offsetPosition = recyclerView.mAdapterHelper.findPositionOffset(position);
            bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
        }

        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final ReLayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (ReLayoutParams) recyclerView.generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!recyclerView.checkLayoutParams(lp)) {
            rvLayoutParams = (ReLayoutParams) recyclerView.generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (ReLayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
        return holder;
    }

    private void attachAccessibilityDelegateOnBind(ViewHolder holder) {
        if (recyclerView.isAccessibilityEnabled()) {
            final View itemView = holder.itemView;
            if (ViewCompat.getImportantForAccessibility(itemView)
                    == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                ViewCompat.setImportantForAccessibility(itemView,
                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
            }
            if (recyclerView.mAccessibilityDelegate == null) {
                return;
            }
            AccessibilityDelegateCompat itemDelegate = recyclerView.mAccessibilityDelegate.getItemDelegate();
            if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) {
                ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate)
                        .saveOriginalDelegate(itemView);
            }
            ViewCompat.setAccessibilityDelegate(itemView, itemDelegate);
        }
    }

    private void invalidateDisplayListInt(ViewHolder holder) {
        if (holder.itemView instanceof ViewGroup) {
            invalidateDisplayListInt((ViewGroup) holder.itemView, false);
        }
    }

    private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) {
        for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
            final View view = viewGroup.getChildAt(i);
            if (view instanceof ViewGroup) {
                invalidateDisplayListInt((ViewGroup) view, true);
            }
        }
        if (!invalidateThis) {
            return;
        }
        if (viewGroup.getVisibility() == View.INVISIBLE) {
            viewGroup.setVisibility(View.VISIBLE);
            viewGroup.setVisibility(View.INVISIBLE);
        } else {
            final int visibility = viewGroup.getVisibility();
            viewGroup.setVisibility(View.INVISIBLE);
            viewGroup.setVisibility(visibility);
        }
    }

    public void recycleView(@NonNull View view) {
        ViewHolder holder = getChildViewHolderInt(view);
        if (holder.isTmpDetached()) {
            recyclerView.removeDetachedView(view, false);
        }
        if (holder.isScrap()) {
            holder.unScrap();
        } else if (holder.wasReturnedFromScrap()) {
            holder.clearReturnedFromScrapFlag();
        }
        recycleViewHolderInternal(holder);
        if (recyclerView.mItemAnimator != null && !holder.isRecyclable()) {
            recyclerView.mItemAnimator.endAnimation(holder);
        }
    }

    void recycleAndClearCachedViews() {
        final int count = mCachedViews.size();
        for (int i = count - 1; i >= 0; i--) {
            recycleCachedViewAt(i);
        }
        mCachedViews.clear();
        if (ALLOW_THREAD_GAP_WORK) {
            recyclerView.mPrefetchRegistry.clearPrefetchPositions();
        }
    }

    void recycleCachedViewAt(int cachedViewIndex) {
        if (DEBUG) {
            Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
        }
        ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
        if (DEBUG) {
            Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
        }
        addViewHolderToRecycledViewPool(viewHolder, true);
        mCachedViews.remove(cachedViewIndex);
    }

    void recycleViewHolderInternal(ViewHolder holder) {
        if (holder.isScrap() || holder.itemView.getParent() != null) {
            throw new IllegalArgumentException(
                    "Scrapped or attached views may not be recycled. isScrap:"
                            + holder.isScrap() + " isAttached:"
                            + (holder.itemView.getParent() != null) + recyclerView.exceptionLabel());
        }

        if (holder.isTmpDetached()) {
            throw new IllegalArgumentException("Tmp detached view should be removed "
                    + "from RecyclerView before it can be recycled: " + holder
                    + recyclerView.exceptionLabel());
        }

        if (holder.shouldIgnore()) {
            throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                    + " should first call stopIgnoringView(view) before calling recycle."
                    + recyclerView.exceptionLabel());
        }
        final boolean transientStatePreventsRecycling = holder
                .doesTransientStatePreventRecycling();
        @SuppressWarnings("unchecked") final boolean forceRecycle = recyclerView.mAdapter != null
                && transientStatePreventsRecycling
                && recyclerView.mAdapter.onFailedToRecycleView(holder);
        boolean cached = false;
        boolean recycled = false;
        if (DEBUG && mCachedViews.contains(holder)) {
            throw new IllegalArgumentException("cached view received recycle internal? "
                    + holder + recyclerView.exceptionLabel());
        }
        if (forceRecycle || holder.isRecyclable()) {
            if (mViewCacheMax > 0
                    && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_REMOVED
                    | ViewHolder.FLAG_UPDATE
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                int cachedViewSize = mCachedViews.size();
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                    recycleCachedViewAt(0);
                    cachedViewSize--;
                }

                int targetCacheIndex = cachedViewSize;
                if (ALLOW_THREAD_GAP_WORK
                        && cachedViewSize > 0
                        && !recyclerView.mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                    int cacheIndex = cachedViewSize - 1;
                    while (cacheIndex >= 0) {
                        int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                        if (!recyclerView.mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                            break;
                        }
                        cacheIndex--;
                    }
                    targetCacheIndex = cacheIndex + 1;
                }
                mCachedViews.add(targetCacheIndex, holder);
                cached = true;
            }
            if (!cached) {
                addViewHolderToRecycledViewPool(holder, true);
                recycled = true;
            }
        } else {
            if (DEBUG) {
                Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                        + "re-visit here. We are still removing it from animation lists"
                        + recyclerView.exceptionLabel());
            }
        }
        recyclerView.mViewInfoStore.removeViewHolder(holder);
        if (!cached && !recycled && transientStatePreventsRecycling) {
            holder.mOwnerRecyclerView = null;
        }
    }

    void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
        recyclerView.clearNestedRecyclerViewIfNotNested(holder);
        View itemView = holder.itemView;
        if (recyclerView.mAccessibilityDelegate != null) {
            AccessibilityDelegateCompat itemDelegate = recyclerView.mAccessibilityDelegate.getItemDelegate();
            AccessibilityDelegateCompat originalDelegate = null;
            if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) {
                originalDelegate =
                        ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate)
                                .getAndRemoveOriginalDelegateForItem(itemView);
            }
            ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
        }
        if (dispatchRecycled) {
            dispatchViewRecycled(holder);
        }
        holder.mOwnerRecyclerView = null;
        getRecycledViewPool().putRecycledView(holder);
    }

    void quickRecycleScrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
        holder.mScrapContainer = null;
        holder.mInChangeScrap = false;
        holder.clearReturnedFromScrapFlag();
        recycleViewHolderInternal(holder);
    }

    void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
        if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                || !holder.isUpdated() || recyclerView.canReuseUpdatedViewHolder(holder)) {
            if (holder.isInvalid() && !holder.isRemoved() && !recyclerView.mAdapter.hasStableIds()) {
                throw new IllegalArgumentException("Called scrap view with an invalid view."
                        + " Invalid views cannot be reused from scrap, they should rebound from"
                        + " recycler pool." + recyclerView.exceptionLabel());
            }
            holder.setScrapContainer(this, false);
            mAttachedScrap.add(holder);
        } else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this, true);
            mChangedScrap.add(holder);
        }
    }

    void unscrapView(ViewHolder holder) {
        if (holder.mInChangeScrap) {
            mChangedScrap.remove(holder);
        } else {
            mAttachedScrap.remove(holder);
        }
        holder.mScrapContainer = null;
        holder.mInChangeScrap = false;
        holder.clearReturnedFromScrapFlag();
    }

    int getScrapCount() {
        return mAttachedScrap.size();
    }

    View getScrapViewAt(int index) {
        return mAttachedScrap.get(index).itemView;
    }

    void clearScrap() {
        mAttachedScrap.clear();
        if (mChangedScrap != null) {
            mChangedScrap.clear();
        }
    }

    ViewHolder getChangedScrapViewForPosition(int position) {
        final int changedScrapSize;
        if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
            return null;
        }
        for (int i = 0; i < changedScrapSize; i++) {
            final ViewHolder holder = mChangedScrap.get(i);
            if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                return holder;
            }
        }
        if (recyclerView.mAdapter.hasStableIds()) {
            final int offsetPosition = recyclerView.mAdapterHelper.findPositionOffset(position);
            if (offsetPosition > 0 && offsetPosition < recyclerView.mAdapter.getItemCount()) {
                final long id = recyclerView.mAdapter.getItemId(offsetPosition);
                for (int i = 0; i < changedScrapSize; i++) {
                    final ViewHolder holder = mChangedScrap.get(i);
                    if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                        holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                        return holder;
                    }
                }
            }
        }
        return null;
    }

    ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
        final int scrapCount = mAttachedScrap.size();
        for (int i = 0; i < scrapCount; i++) {
            final ViewHolder holder = mAttachedScrap.get(i);
            if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                    && !holder.isInvalid() && (recyclerView.mState.mInPreLayout || !holder.isRemoved())) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                return holder;
            }
        }
        if (!dryRun) {
            View view = recyclerView.mChildHelper.findHiddenNonRemovedView(position);
            if (view != null) {
                final ViewHolder vh = getChildViewHolderInt(view);
                recyclerView.mChildHelper.unhide(view);
                int layoutIndex = recyclerView.mChildHelper.indexOfChild(view);
                if (layoutIndex == RecyclerView.NO_POSITION) {
                    throw new IllegalStateException("layout index should not be -1 after "
                            + "unhiding a view:" + vh + recyclerView.exceptionLabel());
                }
                recyclerView.mChildHelper.detachViewFromParent(layoutIndex);
                scrapView(view);
                vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                        | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                return vh;
            }
        }
        final int cacheSize = mCachedViews.size();
        for (int i = 0; i < cacheSize; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            if (!holder.isInvalid() && holder.getLayoutPosition() == position
                    && !holder.isAttachedToTransitionOverlay()) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                if (DEBUG) {
                    Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                            + ") found match in cache: " + holder);
                }
                return holder;
            }
        }
        return null;
    }

    ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
        final int count = mAttachedScrap.size();
        for (int i = count - 1; i >= 0; i--) {
            final ViewHolder holder = mAttachedScrap.get(i);
            if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                if (type == holder.getItemViewType()) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    if (holder.isRemoved()) {
                        if (!recyclerView.mState.isPreLayout()) {
                            holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                    | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                        }
                    }
                    return holder;
                } else if (!dryRun) {
                    mAttachedScrap.remove(i);
                    recyclerView.removeDetachedView(holder.itemView, false);
                    quickRecycleScrapView(holder.itemView);
                }
            }
        }
        final int cacheSize = mCachedViews.size();
        for (int i = cacheSize - 1; i >= 0; i--) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
                if (type == holder.getItemViewType()) {
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    return holder;
                } else if (!dryRun) {
                    recycleCachedViewAt(i);
                    return null;
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    void dispatchViewRecycled(@NonNull ViewHolder holder) {
        if (recyclerView.mRecyclerListener != null) {
            recyclerView.mRecyclerListener.onViewRecycled(holder);
        }
        if (recyclerView.mAdapter != null) {
            recyclerView.mAdapter.onViewRecycled(holder);
        }
        if (recyclerView.mState != null) {
            recyclerView.mViewInfoStore.removeViewHolder(holder);
        }
        if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
    }

    void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
                          boolean compatibleWithPrevious) {
        clear();
        getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
    }

    void offsetPositionRecordsForMove(int from, int to) {
        final int start, end, inBetweenOffset;
        if (from < to) {
            start = from;
            end = to;
            inBetweenOffset = -1;
        } else {
            start = to;
            end = from;
            inBetweenOffset = 1;
        }
        final int cachedCount = mCachedViews.size();
        for (int i = 0; i < cachedCount; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder == null || holder.mPosition < start || holder.mPosition > end) {
                continue;
            }
            if (holder.mPosition == from) {
                holder.offsetPosition(to - from, false);
            } else {
                holder.offsetPosition(inBetweenOffset, false);
            }
            if (DEBUG) {
                Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder "
                        + holder);
            }
        }
    }

    void offsetPositionRecordsForInsert(int insertedAt, int count) {
        final int cachedCount = mCachedViews.size();
        for (int i = 0; i < cachedCount; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder != null && holder.mPosition >= insertedAt) {
                if (DEBUG) {
                    Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder "
                            + holder + " now at position " + (holder.mPosition + count));
                }
                holder.offsetPosition(count, true);
            }
        }
    }

    void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) {
        final int removedEnd = removedFrom + count;
        final int cachedCount = mCachedViews.size();
        for (int i = cachedCount - 1; i >= 0; i--) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder != null) {
                if (holder.mPosition >= removedEnd) {
                    if (DEBUG) {
                        Log.d(TAG, "offsetPositionRecordsForRemove cached " + i
                                + " holder " + holder + " now at position "
                                + (holder.mPosition - count));
                    }
                    holder.offsetPosition(-count, applyToPreLayout);
                } else if (holder.mPosition >= removedFrom) {
                    holder.addFlags(ViewHolder.FLAG_REMOVED);
                    recycleCachedViewAt(i);
                }
            }
        }
    }

    void setViewCacheExtension(ViewCacheExtension extension) {
        mViewCacheExtension = extension;
    }

    void setRecycledViewPool(RecycledViewPool pool) {
        if (mRecyclerPool != null) {
            mRecyclerPool.detach();
        }
        mRecyclerPool = pool;
        if (mRecyclerPool != null && recyclerView.getAdapter() != null) {
            mRecyclerPool.attach();
        }
    }

    RecycledViewPool getRecycledViewPool() {
        if (mRecyclerPool == null) {
            mRecyclerPool = new RecycledViewPool();
        }
        return mRecyclerPool;
    }

    void viewRangeUpdate(int positionStart, int itemCount) {
        final int positionEnd = positionStart + itemCount;
        final int cachedCount = mCachedViews.size();
        for (int i = cachedCount - 1; i >= 0; i--) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder == null) {
                continue;
            }
            final int pos = holder.mPosition;
            if (pos >= positionStart && pos < positionEnd) {
                holder.addFlags(ViewHolder.FLAG_UPDATE);
                recycleCachedViewAt(i);
            }
        }
    }

    void markKnownViewsInvalid() {
        final int cachedCount = mCachedViews.size();
        for (int i = 0; i < cachedCount; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder != null) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
                holder.addChangePayload(null);
            }
        }
        if (recyclerView.mAdapter == null || !recyclerView.mAdapter.hasStableIds()) {
            recycleAndClearCachedViews();
        }
    }

    void clearOldPositions() {
        final int cachedCount = mCachedViews.size();
        for (int i = 0; i < cachedCount; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            holder.clearOldPosition();
        }
        final int scrapCount = mAttachedScrap.size();
        for (int i = 0; i < scrapCount; i++) {
            mAttachedScrap.get(i).clearOldPosition();
        }
        if (mChangedScrap != null) {
            final int changedScrapCount = mChangedScrap.size();
            for (int i = 0; i < changedScrapCount; i++) {
                mChangedScrap.get(i).clearOldPosition();
            }
        }
    }

    void markItemDecorInsetsDirty() {
        final int cachedCount = mCachedViews.size();
        for (int i = 0; i < cachedCount; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            ReLayoutParams layoutParams = (ReLayoutParams) holder.itemView.getLayoutParams();
            if (layoutParams != null) {
                layoutParams.mInsetsDirty = true;
            }
        }
    }
}